#include "common.hpp"

#include "bit_model.hpp"
#include "byte_model.hpp"
#include "range_coder.hpp"

#define HASH(x) (h = ((h*160) + (x)) & 0xfffff)

#define READRAW() next = (next << 8) + in.get()

#define READ() { \
  HASH((next >> 24) & 0xff); \
  READRAW(); \
  cxt[phashes[curphash]] = uint32(next >> 32); \
  phashes[curphash] = h; \
  curphash = (curphash+1)&3; \
}

#if 0
#define READ() { \
  HASH((next >> 24) & 0xff); \
  READRAW(); \
  cxt[h2] = uint32(next >> 32); \
  h2 = ((h2*160) + (next >> 56)) & 0xfffff; \
}
#endif

struct Model
{
	ByteModelMultiSymbol literalModel[256];
	BitModelFast bitModels[8 * 8];
};

// Compress from in to out.  out should be positioned past the header.
void glz_compress(ByteReader in, ByteWriter writer)
{
	Coder1 c(writer);

	std::auto_ptr<Model> model(new Model);

	int rep = 0;

	uint32 h = 0; // hash of last 4 bytes

	uint32 phashes[4] = {};
	int curphash = 0;

	//uint32 h2 = 0;
  
	std::vector<uint32> cxt(0x100000);

	uint64 next = 0;
	READRAW(); READRAW(); READRAW(); READRAW();

	// TODO: Encode first 4 as literals

	READ(); READ(); READ(); READ();
	
	while (1)
	{
		if (in.empty())
			break;

		uint32 pred = cxt[h];

		uint32 unseen = uint32(next);

		BitModelFast* curModel = &model->bitModels[rep * 8];
		//BitModelFast* curModel = &bitModels[0];

		if(unseen == pred)
		{
			curModel[0].encode(c, 1);
			curModel[1].encode(c, 1);
			curModel[2].encode(c, 1);
			curModel[3].encode(c, 1);
			curModel[4].encode(c, 0);

			READ(); READ(); READ(); READ();
			if(rep < 7)
				++rep;
			continue;
		}
		
		uint32 m = unseen ^ pred;
		if(m < 0x10000)
		{
			if(m < 0x100)
			{
				curModel[0].encode(c, 1);
				curModel[1].encode(c, 1);
				curModel[2].encode(c, 1);
				curModel[3].encode(c, 0);

				// Match 3
				READ(); READ(); READ();
			}
			else
			{
				// Match 2

				curModel[0].encode(c, 1);
				curModel[1].encode(c, 1);
				curModel[2].encode(c, 0);

				READ(); READ();
			}
		}
		else if(m < 0x1000000)
		{
			// Match 1

			curModel[0].encode(c, 1);
			curModel[1].encode(c, 0);

			READ();
		}
		else
		{
			// Literals
			curModel[0].encode(c, 0);

			model->literalModel[(next >> 32) & 0xff].encode(c, (next >> 24) & 0xff);

			READ();
		}

		rep = 0;
	}

	c.flush();
}
